﻿namespace ShellNavigator.Client.Models
{
  #region

  using System;
  using System.Collections.Generic;
  using System.Collections.ObjectModel;
  using System.IO;
  using System.Windows;
  using System.Windows.Input;

  using Microsoft.Win32;

  using ShellNavigator.Client.Core;
  using ShellNavigator.Client.Utils;

  #endregion

  public class MainWindowModel : DependencyObject
  {
    #region Constants and Fields

    public static readonly DependencyProperty ResultProperty = DependencyProperty.Register(
      "Result", typeof(InfoBase), typeof(MainWindowModel), new UIPropertyMetadata(OnResultChanged));

    public static DependencyProperty DirectoryProperty = DependencyProperty.Register(
      "Directory", typeof(string), typeof(MainWindowModel), new PropertyMetadata(default(string)));

    public static DependencyProperty FileOrMaskProperty = DependencyProperty.Register(
      "FileOrMask", typeof(string), typeof(MainWindowModel), new PropertyMetadata(default(string), OnFileOrMaskChanged));

    public static DependencyProperty IsMatchCaseProperty = DependencyProperty.Register(
      "IsMatchCase", typeof(bool), typeof(MainWindowModel), new PropertyMetadata(default(bool)));

    public static DependencyProperty IsPlainTextProperty = DependencyProperty.Register(
      "IsPlainText", typeof(bool), typeof(MainWindowModel), new PropertyMetadata(default(bool)));

    public static DependencyProperty TargetProperty = DependencyProperty.Register(
      "Target", typeof(AppTargets), typeof(MainWindowModel), new PropertyMetadata(default(AppTargets)));

    public static DependencyProperty TextFilterProperty = DependencyProperty.Register(
      "TextFilter", typeof(string), typeof(MainWindowModel), new PropertyMetadata(default(string)));

    private readonly ObservableCollection<string> directoryHistory = new ObservableCollection<string>();

    private readonly ObservableCollection<string> fileOrMaskHistory = new ObservableCollection<string>();

    private readonly bool initState;
    
      private readonly ObservableCollection<InfoBase> searchResults = new ObservableCollection<InfoBase>();

    private readonly ObservableCollection<string> textFilterHistory = new ObservableCollection<string>();

    #endregion

    #region Constructors and Destructors

    public MainWindowModel()
    {
      this.initState = true;

      this.ReduceDirectory = new DelegateCommand(this.ExecuteReduceDirectory, null);
      this.OpenFileDialog = new DelegateCommand(this.ExecuteOpenFileDialog, null);
      this.Find = new DelegateCommand(this.ExecuteFind, null);
      this.RunAction = new DelegateCommand(this.ExecuteRunAction, null);

      this.CopyFile = new DelegateCommand(this.ExecuteCopyFileAction, o => this.Result != null);
      this.CopyAssembly = new DelegateCommand(
        this.ExecuteCopyAssemblyAction, o => this.Result != null && this.Target == AppTargets.Project);

      this.initState = false;
    }

    public MainWindowModel(Request request, IEnumerable<InfoBase> results)
      : this()
    {
      this.initState = true;

      this.FileOrMask = request.Filemask;
      this.Directory = request.Directory;
      this.Target = request.Target;
      this.TextFilter = request.Text;
      this.IsMatchCase = request.IsMatchCase;
      this.IsPlainText = request.IsPlainText;
      this.SearchResults.Assign(results);

      this.initState = false;
    }

    #endregion

    #region Public Properties

    /// <summary>
    ///     Gets or sets the copy.
    /// </summary>
    /// <value>The find.</value>
    public DelegateCommand CopyAssembly { get; set; }

    /// <summary>
    ///     Gets or sets the copy.
    /// </summary>
    /// <value>The find.</value>
    public DelegateCommand CopyFile { get; set; }

    public string Directory
    {
      get
      {
        return (string)this.GetValue(DirectoryProperty);
      }

      set
      {
        this.SetValue(DirectoryProperty, value);
      }
    }

    public ObservableCollection<string> DirectoryHistory
    {
      get
      {
        return this.directoryHistory;
      }
    }

    public string FileOrMask
    {
      get
      {
        return (string)this.GetValue(FileOrMaskProperty);
      }

      set
      {
        this.SetValue(FileOrMaskProperty, value);
      }
    }

    public ObservableCollection<string> FileOrMaskHistory
    {
      get
      {
        return this.fileOrMaskHistory;
      }
    }

    /// <summary>
    ///     Gets or sets the find.
    /// </summary>
    /// <value>The find.</value>
    public DelegateCommand Find { get; set; }

    public bool IsMatchCase
    {
      get
      {
        return (bool)this.GetValue(IsMatchCaseProperty);
      }

      set
      {
        this.SetValue(IsMatchCaseProperty, value);
      }
    }

    public bool IsPlainText
    {
      get
      {
        return (bool)this.GetValue(IsPlainTextProperty);
      }

      set
      {
        this.SetValue(IsPlainTextProperty, value);
      }
    }

    public DelegateCommand OpenDirectory { get; set; }

    /// <summary>
    ///     Gets or sets the open file dialog.
    /// </summary>
    /// <value>The open file dialog.</value>
    public DelegateCommand OpenFileDialog { get; set; }

    public DelegateCommand ReduceDirectory { get; private set; }

    public InfoBase Result
    {
      get
      {
        return (InfoBase)this.GetValue(ResultProperty);
      }

      set
      {
        this.SetValue(ResultProperty, value);
      }
    }

    /// <summary>
    ///     Gets or sets the run action.
    /// </summary>
    /// <value>The run action.</value>
    public DelegateCommand RunAction { get; set; }

    /// <summary>
    ///     Gets the search results.
    /// </summary>
    /// <value>The search results.</value>
    public ObservableCollection<InfoBase> SearchResults
    {
      get
      {
        return this.searchResults;
      }
    }

    public AppTargets Target
    {
      get
      {
        return (AppTargets)this.GetValue(TargetProperty);
      }

      set
      {
        this.SetValue(TargetProperty, value);
      }
    }

    public string TextFilter
    {
      get
      {
        return (string)this.GetValue(TextFilterProperty);
      }

      set
      {
        this.SetValue(TextFilterProperty, value);
      }
    }

    public ObservableCollection<string> TextFilterHistory
    {
      get
      {
        return this.textFilterHistory;
      }
    }

    #endregion

    #region Methods

    public static void ExecuteOnError(Exception ex)
    {
      if (ex is AggregateException)
      {
        MessageBox.Show(ex.InnerException.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
      }
      else
      {
        MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
      }
    }

    private static void OnFileOrMaskChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      var model = (MainWindowModel)d;
      if (model.initState)
      {
        return;
      }

      model.UpdateFileOrMask((string)e.NewValue);
    }

    private static void OnResultChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      var model = (MainWindowModel)d;
      if (model.initState)
      {
        return;
      }

      model.CopyAssembly.InvalidatePreview();
      model.CopyFile.InvalidatePreview();
    }

    private void ExecuteCopyAssemblyAction(object obj)
    {
      this.Result.RunAction(AppActions.CopyAssembly);
    }

    private void ExecuteCopyFileAction(object obj)
    {
      this.Result.RunAction(AppActions.CopyFile);
    }

    private void ExecuteFind(object obj)
    {
      this.SearchResults.Clear();

      var request = new Request
        {
          Action = AppActions.OpenSolution, 
          Target = this.Target, 
          Directory = this.Directory, 
          Filemask = this.FileOrMask, 
          Text = this.TextFilter, 
          IsPlainText = this.IsPlainText, 
          IsMatchCase = this.IsMatchCase
        };

      SearchEngine.VisualSearch(
        request, (args, results) => this.SearchResults.Assign(results), ExecuteOnError);
    }

    private void ExecuteReduceDirectory(object obj)
    {
      var parent = Path.GetDirectoryName(this.Directory);

      if (!string.IsNullOrEmpty(parent))
      {
        this.UpdateDirectory(parent);
      }
    }

    private void ExecuteOpenFileDialog(object obj)
    {
      var dlg = new OpenFileDialog { Filter = "All Files(*.*)|*.*", CheckFileExists = true };

      var result = dlg.ShowDialog();

      if (result == true)
      {
        this.UpdateFileOrMask(dlg.FileName);
      }
    }

    private void ExecuteRunAction(object obj)
    {
      var execute = true;
      if (obj is KeyEventArgs)
      {
        var arg = (KeyEventArgs)obj;
        execute = arg.Key == Key.Enter;
      }

      if (!execute)
      {
        return;
      }
      
      switch (this.Target)
      {
        case AppTargets.Solution:
          this.Result.RunAction(AppActions.OpenSolution);
          break;
        case AppTargets.Project:
          this.Result.RunAction(AppActions.OpenProject);
          break;
        case AppTargets.File:
          this.Result.RunAction(AppActions.OpenFile);
          break;
      }
    }

    private void UpdateDirectory(string directory)
    {
      if (directory == this.Directory)
      {
        return;
      }

      if (!string.IsNullOrEmpty(this.Directory))
      {
        this.DirectoryHistory.Add(this.Directory);
      }

      this.Directory = directory;
    }

    private void UpdateFileOrMask(string fileName)
    {
      if (fileName == this.FileOrMask)
      {
        return;
      }

      if (!string.IsNullOrEmpty(this.FileOrMask))
      {
        this.FileOrMaskHistory.Add(this.FileOrMask);
      }

      var request = new Request { Filemask = fileName, };

      try
      {
        SearchEngine.Validate(request);
      }
      catch
      {
      }

      this.FileOrMask = request.Filemask;

      if (!string.IsNullOrEmpty(request.Directory))
      {
        this.UpdateDirectory(request.Directory);
      }
    }

    #endregion
  }
}